three.js 测距功能实现

2023/09/27 17:34:00

three.js 测距功能实现

three.js 测距功能实现

背景需求

  1. 对三维模型进行测距;
  2. 共两个按钮:测距、清空;

代码实现

  1. html设置

    <el-button type="primary" style="position: absolute; z-index: 999; right: 80px" @click="measureDis">测距</el-button>
    <el-button type="primary" style="position: absolute; z-index: 999; right: 5px" @click="clearDraw()">清除</el-button>
    
  2. data设置

    data() {
        return {
          raycaster: new THREE.Raycaster(),
          mouse: new THREE.Vector2(),
          group: new THREE.Group(),
          labelRenderer: new CSS2DRenderer(),
          groups: [],
          points: [],
          line: null,
          measuring: false,
        };
      }
    
  3. init初始化函数

    init() {
        //普通2D标签
        let contain = document.getElementById("container"); // 获取three.js的显示框id
        this.labelRenderer.setSize(contain.offsetWidth, contain.offsetHeight); // 设置three.js的显示框大小
        this.labelRenderer.domElement.style.position = "absolute"; // 设置three.js的显示框位置
        this.labelRenderer.domElement.style.top = "8%";
        this.labelRenderer.domElement.style.right = "1%";
        this.labelRenderer.domElement.style.pointerEvents = "none"; // 避免HTML标签遮挡三维场景的鼠标事件
        document.body.appendChild(this.labelRenderer.domElement); // 将labelRenderer添加到文档主体
    }
    
  4. render标签渲染

    this.labelRenderer.render(this.scene, this.camera);
    
  5. mounted设置

    mounted() {
        this.init();
        this.animate();
    
        window.addEventListener("resize", this.onWindowResize);
        // 监测click
        window.addEventListener("click", (event) => {
          if (this.measuring) {
            this.onclick(event);
          }
        });
    
        this.scene.add(this.group);
      },
    
  6. methods设置

    // 窗口大小改变时更新的渲染器和相机,保持渲染画面的正确
    onWindowResize() {
        const newWidth = window.innerWidth;
        const newHeight = window.innerHeight;
    
        this.camera.aspect = newWidth / newHeight;
        this.camera.updateProjectionMatrix();
    
        this.renderer.setSize(newWidth, newHeight);
    },
    // click捕捉函数
    onclick(event) {
        // 获取鼠标点击的坐标
        const mouseX = event.clientX;
        const mouseY = event.clientY;
    
        // 获取three.js场景的容器
        let container = document.getElementById("container");
        // 将鼠标坐标转换为Three.js坐标系中的位置
        this.mouse.x = ((mouseX - container.getBoundingClientRect().left) / container.offsetWidth) *2 - 1;
        this.mouse.y = -((mouseY - container.getBoundingClientRect().top) / container.offsetHeight) * 2 + 1;
    
        // 设置穿透的射线
        this.raycaster.params.Points.threshold = 0.3;
        this.raycaster.setFromCamera(this.mouse, this.camera);
        // 获取射线和场景中对象的交点
        const intersects = this.raycaster.intersectObjects(
            this.scene.children,
            false
        );
        // 如果存在多个交点数则取第一个
        if (intersects.length > 0) {
            const point = intersects[0].point;
            this.addPoint(point.x, point.y, point.z);
        }
    },
    addPoint(x, y, z) {
        // 创建点对象
        const point = this.createPoint(x, y, z);
        // 一个为场景对象
        this.group.add(point);
        this.groups.push(point);
        console.log(this.group);
        
        // 将点的坐标保存到数组中
        this.points.push(new THREE.Vector3(x, y, z));
    
        // 更新测量线
        this.updateMeasurementLine();
    },
    updateMeasurementLine() {
        if (this.points.length >= 2) {
            // 创建线的几何体和材质
            const lineGeometry = new THREE.BufferGeometry().setFromPoints(
                this.points
            );
            const lineMaterial = new THREE.LineBasicMaterial({
                color: 0xf9f8ed,
            });
    
            // 创建线对象
            if (!this.line) {
                this.line = new THREE.Line(lineGeometry, lineMaterial);
                this.group.add(this.line);
                this.groups.push(this.line);
            } else {
                //   this.line.geometry.dispose();
                this.line.geometry = lineGeometry;
            }
    
            // 计算距离并显示在界面上
            const distance = this.points[this.points.length - 2].distanceTo(
                this.points[this.points.length - 1]
            );
            console.log(`距离: ${distance}`);
    
            // 添加CSS 2DObject标签
            // 求中点坐标
            let centerX = (this.points[this.points.length - 2].x + this.points[this.points.length - 1].x) / 2;
            let centerY = (this.points[this.points.length - 2].y + this.points[this.points.length - 1].y) / 2;
            let centerZ = (this.points[this.points.length - 2].z + this.points[this.points.length - 1].z) / 2;
            
            //创建标签
            const container = document.getElementById("container");
            const earthDiv = document.createElement("div");
            earthDiv.className = "label_name"; // 定义的类名可以通过下面的style进行设置样式
            earthDiv.textContent = distance.toFixed(5) + " m";
            earthDiv.style.marginTop = "-1em";
            const label2D = new CSS2DObject(earthDiv);
            label2D.name = distance.toFixed(0);
            label2D.position.set(centerX, centerY, centerZ); //标签标注在obj世界坐标
            container.appendChild(label2D.element);
            this.group.add(label2D); //标签插入场景
            this.groups.push(label2D);
    
            // this.labelRenderer.scene.add(label2D);
            this.labelRenderer.render(this.scene, this.camera);
        }
    },
    measureDis() {
        this.measuring = !this.measuring;
    },
    clearDraw() {
        console.log(this.scene.children);
        // 移除this.groups中保存的所有对象
        // 注:不直接使用this.scene.children进行遍历,是因为遍历结果不全面无法得到页面所有Mesh有待商催
        this.groups.forEach((object) => {
            console.log(object);
            if (object.type === "Mesh" || object.type === "Line") {
                console.log(object.type);
                // 释放几何体和样式资源
                object.geometry.dispose();
                object.material.dispose();
                this.group.remove(object);
    
                if (object.type === "Line") {
                    this.line = null;
                }
            } else {
                // let label = this.scene.getObjectByName(object.name);
                object.parent.remove(object);
            }
        });
        this.points = [];
        this.measuring = !this.measuring;
    },
    createPoint(x, y, z, config = { color: 0xf9f8ed, size: 0.8 }) {
        let mat = new THREE.MeshBasicMaterial({
            color: config.color || 0xf9f8ed,
        });
        let sphereGeometry = new THREE.SphereGeometry(config.size || 0.3, 32, 32);
        let sphere = new THREE.Mesh(sphereGeometry, mat);
        sphere.position.set(x, y, z);
        return sphere;
    },
    
  7. style设置

    .label_name {
        color: #f9f8ed;
        font-size: 16px;
        z-index: 9999 !important;
    }